﻿/*******************************************************************************

    Units of Measure for C# applications

    Copyright (C) 2013 Marek Aniola

    This program is provided to you under the terms of the license
    as published at http://unitsofmeasure.codeplex.com/license


********************************************************************************/
using System;
using System.Diagnostics;
using Man.UnitsOfMeasure;

namespace Test
{
	using QDouble = Man.UnitsOfMeasure.Quantity<double>;

	class Program
	{
		private static Stopwatch sw = new Stopwatch();
		private static SIUnitsDouble si = new SIUnitsDouble();	// sample units of measure

		static void Main(string[] args)
		{
			Console.WriteLine(
				"Units of Measure for C# applications. Copyright (©) 2013 Marek Anioła.\n" +
				"This program is provided to you under the terms of the license\n" +
				"as published at http://unitsofmeasure.codeplex.com/license."
			);

			do
			{
				Console.WriteLine("\nTrajectory of a projectile (AK-47):\n");
				double performanceRatio = 0.0;

				for (double angle = 0.0; angle < 90.0; angle++)
				{
					long slow = QuantityCalculationsTime(si.Degree[angle]);	// with units
					long fast = PlainCalculationsTime(angle);	// w/o units (normal)

					Console.WriteLine("; quantities/plain = {0:0.0}", (double)slow / (double)fast);

					performanceRatio += (double)slow / (double)fast;
				}

				Console.WriteLine("\nAverage performance ratio quantities/plain = {0:0.0}.", performanceRatio / 90.0);
				Console.WriteLine("\nPress Esc to quit, any other key to retry...");
			}
			while (Console.ReadKey().Key != ConsoleKey.Escape);
		}

		static long QuantityCalculationsTime(QDouble angle)
		{
			sw.Restart();

			QDouble tmax, xmax, ymax;
			TrajectoryQuantityCalculation(angle, out tmax, out xmax, out ymax);

			sw.Stop();
			long ticks = sw.ElapsedTicks;

			Console.WriteLine(
				"{0}: {1}, xmax {2}, ymax {3} - {4} ticks (quantities)",
				angle, tmax.ToString("{0:F0}{1}{2}"), xmax.ToString("{0:F0}{1}{2}"), ymax.ToString("{0:F0}{1}{2}"), ticks);

			return ticks;
		}

		static long PlainCalculationsTime(double angle)
		{
			sw.Restart();

			double tmax, xmax, ymax;
			TrajectoryPlainCalculation(angle, out tmax, out xmax, out ymax);

			sw.Stop();
			long ticks = sw.ElapsedTicks;

			Console.Write(
				"{0}°: {1:F0} s, xmax {2:F0} m, ymax {3:F0} m - {4} ticks (plain)",
				angle, tmax, xmax, ymax, ticks);

			return ticks;
		}

		static void TrajectoryQuantityCalculation(QDouble angleDegree, out QDouble tmax, out QDouble xmax, out QDouble ymax)
		{
			QDouble g = si.MeterPerSec2[9.81];		// the gravitational acceleration
			QDouble v = si.MeterPerSec[715.0];		// the velocity at which the projectile is launched (AK-47)
			QDouble h = si.Meter[0.0];				// the initial height of the projectile
			QDouble angle = si.Radian[angleDegree];	// the angle at which the projectile is launched

			// the time it takes for the projectile to finish its trajectory:
			tmax = si.Second[(v * Math.Sin(angle.Value) + QDouble.Sqrt(QDouble.Pow(v * Math.Sin(angle.Value), 2) + 2.0 * g * h)) / g];

			ymax = h;
			for (var t = si.Second[0.0]; t < tmax; t++)
			{
				QDouble y = h + v * Math.Sin(angle.Value) * t - g * t * t / 2.0;
				if (y > ymax) ymax = y;
			}

			// the total horizontal distance traveled by the projectile
			xmax = si.Meter[v * Math.Cos(angle.Value) * tmax];
		}

		static void TrajectoryPlainCalculation(double angleDegree, out double tmax, out double xmax, out double ymax)
		{
			double g = 9.81;	// the gravitational acceleration
			double v = 715.0;	// the velocity at which the projectile is launched (AK-47)
			double h = 0.0;		// the initial height of the projectile
			double angle = angleDegree * Math.PI / 180.0;	// the angle at which the projectile is launched

			// the time it takes for the projectile to finish its trajectory:
			tmax = (v * Math.Sin(angle) + Math.Sqrt(Math.Pow(v * Math.Sin(angle), 2) + 2.0 * g * h)) / g;

			ymax = h;
			for (double t = 0.0; t < tmax; t++)
			{
				double y = h + v * Math.Sin(angle) * t - g * t * t / 2.0;
				if (y > ymax) ymax = y;
			}

			// the total horizontal distance traveled by the projectile
			xmax = v * Math.Cos(angle) * tmax;
		}
	}
}
